
package reggex;

import java.util.List;

import lexer.Token;
import lexer.Tokenizer;

import parser.Production;
import parser.Parser;
import parser.SyntaxTranslator;
import parser.SemanticProduction;

import parser.lr.LRAction;
import parser.lr.LRParser;

import automaton.NDState;
import automaton.NondeterministicAutomaton;

public class Reggex {
	
	public abstract static class RProduction extends SemanticProduction<ReggexAttributes> {
		public RProduction(String production) { super(production); }
	}
	
	public static Parser parser() {
		return new LRParser("EFGH", 5, getProductions(), getTable(), 1);
	}
	
	public static Tokenizer tokenizer() {
		return new ReggexTokenizer();
	}
	
	public static SyntaxTranslator<ReggexAttributes> translator() {
		return new ReggexTranslator();
	}
	
	public static class IdentityProduction extends SemanticProduction<ReggexAttributes> {
		private final int index;
		
		public IdentityProduction(String production, int index) {
			super(production);
			this.index = index;
		}
		
		public ReggexAttributes synthesize(List<ReggexAttributes> children) {
			return children.get(index);
		}
	}
	
	public static Production[] getProductions() {
		return new Production[] {
			
			// una expresión es una expresión seguida de un pipe y una expresión simple...
			new RProduction("E->E|F") {
				public ReggexAttributes synthesize(List<ReggexAttributes> children) {
					return new ReggexAttributes(
						unionAutomaton(
							children.get(0).getAutomaton(),
							children.get(2).getAutomaton()
						)
					);
				}
			},
			
			// ... o una sola expresión simple;
			new IdentityProduction("E->F", 0),
			
			// una expresión simple es una expresión simple seguida de una palabra...
			new RProduction("F->FG") {
				public ReggexAttributes synthesize(List<ReggexAttributes> children) {
					return new ReggexAttributes(
						concatAutomaton(
							children.get(0).getAutomaton(),
							children.get(1).getAutomaton()
						)
					);
				}
			},
			
			// ... o una sola palabra;
			new IdentityProduction("F->G", 0),
			
			// una palabra es una palabra cuantificada...
			new RProduction("G->G+") {
				public ReggexAttributes synthesize(List<ReggexAttributes> children) {
					Token operator = children.get(1).getToken();
					NondeterministicAutomaton<Character> automaton =
						children.get(0).getAutomaton();
					
					if("+".equals(operator.getValue())) {
						return new ReggexAttributes(transitiveClosure(automaton));
					} else if("*".equals(operator.getValue())) {
						return new ReggexAttributes(kleeneClosure(automaton));
					} else if("?".equals(operator.getValue())) {
						return new ReggexAttributes(optional(automaton));
					} else {
						throw new RuntimeException("Invalid operator");
					}
				}
			},
			
			// ... o un átomo;
			new IdentityProduction("G->H", 0),
			
			// un átomo es un caracter...
			new IdentityProduction("H->c", 0),
			
			// o una expresión entre paréntesis.
			new IdentityProduction("H->(E)", 1)
		};
	}
	
	
	
	private static NondeterministicAutomaton<Character> unionAutomaton(
			NondeterministicAutomaton<Character> c1,
			NondeterministicAutomaton<Character> c2) {
		
		final NDState<Character> s1 = new NDState<Character>(false);
		
		NondeterministicAutomaton<Character> ret = new NondeterministicAutomaton<Character>(s1);
		
		for(NDState<Character> state : c1.getStates()) {
			ret.addState(state);
		}
		for(NDState<Character> state : c2.getStates()) {
			ret.addState(state);
		}
		
		s1.addTransition(null, c1.getInitialState());
		s1.addTransition(null, c2.getInitialState());
		
		return ret;
	}
	
	private static NondeterministicAutomaton<Character> concatAutomaton(
			NondeterministicAutomaton<Character> c1,
			NondeterministicAutomaton<Character> c2) {
		
		NondeterministicAutomaton<Character> ret =
			new NondeterministicAutomaton<Character>(c1.getInitialState());
		
		for(NDState<Character> state : c1.getStates()) {
			ret.addState(state);
		}
		for(NDState<Character> state : c2.getStates()) {
			ret.addState(state);
		}
		
		for(NDState<Character> state : c1.getFinalStates()) {
			state.setAcceptor(false);
			state.addTransition(null, c2.getInitialState());
		}
		
		return ret;
	}
	
	private static NondeterministicAutomaton<Character> transitiveClosure(
			NondeterministicAutomaton<Character> c1) {
		NondeterministicAutomaton<Character> ret =
			new NondeterministicAutomaton<Character>(c1.getInitialState());
		
		for(NDState<Character> state : c1.getStates()) {
			ret.addState(state);
		}
		
		for(NDState<Character> state : c1.getFinalStates()) {
			state.addTransition(null, c1.getInitialState());
		}
		
		return ret;
	}
	
	private static NondeterministicAutomaton<Character> kleeneClosure(
			NondeterministicAutomaton<Character> c1) {
		
		NDState<Character> initial = new NDState<Character>(true);
		
		NondeterministicAutomaton<Character> ret =
			new NondeterministicAutomaton<Character>(initial);
		
		for(NDState<Character> state : c1.getStates()) {
			ret.addState(state);
		}
		
		initial.addTransition(null, c1.getInitialState());
		
		for(NDState<Character> state : c1.getFinalStates()) {
			state.addTransition(null, initial);
		}
		
		return ret;
	}
	
	private static NondeterministicAutomaton<Character> optional(
			NondeterministicAutomaton<Character> c1) {
		NDState<Character> initial = new NDState<Character>(true);
		
		NondeterministicAutomaton<Character> ret =
			new NondeterministicAutomaton<Character>(initial);
		
		for(NDState<Character> state : c1.getStates()) {
			ret.addState(state);
		}
		
		initial.addTransition(null, c1.getInitialState());
		
		return ret;
	}
	
	public static LRAction[][] getTable() {
		return new LRAction[][] {
			/*            $        char        op          (          )          |          E          F          G          H    */
			/*--------------------------------------------------------------------------------------------------------------------*/
			/*  1 */{   null   , shift(6) ,   null   , shift(7) ,   null   ,   null   , go_to(2) , go_to(3) , go_to(4) , go_to(5) },
			/*  2 */{ accept() ,   null   ,   null   ,   null   ,   null   , shift(9) ,   null   ,   null   ,   null   ,   null   },
			/*  3 */{ reduce(2), shift(6) ,   null   , shift(7) , reduce(2), reduce(2),   null   ,   null   , go_to(10), go_to(5) },
			/*  4 */{ reduce(4), reduce(4), shift(11), reduce(4), reduce(4), reduce(4),   null   ,   null   ,   null   ,   null   },
			/*  5 */{ reduce(6), reduce(6), reduce(6), reduce(6), reduce(6), reduce(6),   null   ,   null   ,   null   ,   null   },
			/*  6 */{ reduce(7), reduce(7), reduce(7), reduce(7), reduce(7), reduce(7),   null   ,   null   ,   null   ,   null   },
			/*  7 */{   null   , shift(6) ,   null   , shift(7) ,   null   ,   null   , go_to(8) , go_to(3) , go_to(4) , go_to(5) },
			/*  8 */{   null   ,   null   ,   null   ,   null   , shift(12), shift(9) ,   null   ,   null   ,   null   ,   null   },
			/*  9 */{   null   , shift(6) ,   null   , shift(7) ,   null   ,   null   ,   null   , go_to(13), go_to(4) , go_to(5) },
			/* 10 */{ reduce(3), reduce(3), shift(11), reduce(3), reduce(3), reduce(3),   null   ,   null   ,   null   ,   null   },
			/* 11 */{ reduce(5), reduce(5), reduce(5), reduce(5), reduce(5), reduce(5),   null   ,   null   ,   null   ,   null   },
			/* 12 */{ reduce(8), reduce(8), reduce(8), reduce(8), reduce(8), reduce(8),   null   ,   null   ,   null   ,   null   },
			/* 13 */{ reduce(1), shift(6) ,   null   , shift(7) , reduce(1), reduce(1),   null   ,   null   , go_to(10), go_to(5) }
		};
	}
	
	private static LRAction shift(int value) {
		return new LRAction(LRAction.Type.SHIFT, value);
	}
	
	private static LRAction reduce(int value) {
		return new LRAction(LRAction.Type.REDUCE, value);
	}
	
	private static LRAction go_to(int value) {
		return new LRAction(LRAction.Type.GOTO, value);
	}
	
	private static LRAction accept() {
		return new LRAction(LRAction.Type.ACCEPT, 0);
	}
}
